package cen5501c.client;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;

import cen5501c.utilities.FileFraction;
import cen5501c.utilities.MD5;

import cen5501c.common.FileContentCmd;
import cen5501c.common.P2PException;

public class FileManager {
	private static String path = "FileStorage/";
	private static String fileNameEnding = "_end";
	private static File directory = null;
	private static Map<String, TreeSet<FileFraction>> hashToFF = null;
	static {
		hashToFF = new HashMap<String, TreeSet<FileFraction>>();
		directory = new File(path);
		if (!directory.exists())
			directory.mkdir();
		File[] files = directory.listFiles();
		for (File f : files) {
			FileFraction ff = null;
			try {
				ff = new FileFraction(f);
			} catch (Exception e) {
				continue;
			}
			TreeSet<FileFraction> tsff = hashToFF.get(ff.getHash());
			if (tsff == null)
				tsff = new TreeSet<FileFraction>();
			tsff.add(ff);
			hashToFF.put(ff.getHash(), tsff);
		}
	}

	private void mergeFileFraction(FileFraction ff) throws P2PException{
		if (ff.isComplete()) {
			String hashExpected = ff.getHash();
			String hashActual = MD5.md5code(ff.getFile());
			System.out.println("File downloading completed, check integrity...");
			if (!hashActual.equalsIgnoreCase(hashExpected)) {
				System.out.println("Expected hash=" + hashExpected
						+ " actual hash=" + hashActual.toUpperCase());
				System.out.println("file corrupted");
				ff.getFile().delete();
				throw new P2PException("file corrupted");
			}
			System.out.println("File passed integrity check.");
			File ori = ff.getFile();
			String tempName = ff.getFile().getName();
			int index=0;
			while(index!=tempName.length()){
//				System.out.println(tempName.charAt(index));
				if(tempName.charAt(index)=='['&&tempName.charAt(index+33)==']'){
					System.out.println(tempName.charAt(index));
					break;
				}
				++index;
			}
			System.out.println(tempName);
			tempName=tempName.substring(0,index);
			System.out.println(tempName);
//			tempName = tempName.substring(0, tempName.indexOf(".txt[") + 4);
			File dest = new File(path + tempName);
			ori.renameTo(dest);
			ff.setFileName(tempName);
			ff.setFile(new File(path + tempName));
			System.out.println("File "+tempName+" is completely downloaded.");
			return;
		}
		String hash = ff.getHash();
		TreeSet<FileFraction> tfr = hashToFF.get(hash);
		if (tfr == null) {
			tfr = new TreeSet<FileFraction>();
			tfr.add(ff);
			hashToFF.put(hash, tfr);
			return;
		}
		// int counter=0;
		Iterator<FileFraction> iff = tfr.iterator();
		FileFraction start = null;
		FileFraction end = null;
		while (iff.hasNext()) {
			FileFraction tempf = iff.next();
			if (start == null) {
				if (tempf.getStart() >= ff.getStart()
						&& tempf.getStart() <= ff.getEnd()) {
					start = ff;
					// if(end==null)end=ff;
					if (ff.getEnd() <= tempf.getEnd()) {
						end = tempf;
					} else {
						end = ff;
					}
				} else if (ff.getStart() >= tempf.getStart()
						&& ff.getStart() <= tempf.getEnd()) {
					start = tempf;
					// if(end==null)end=ff;
					if (tempf.getEnd() <= ff.getEnd()) {
						end = ff;
					} else {
						end = tempf;
					}
				}
				if (start == null)
					continue;
			}
			if (end.getEnd() >= tempf.getStart() - 1
					&& end.getEnd() < tempf.getEnd()) {
				end = tempf;
			}
		}
		if (start == null) {
			tfr.add(ff);
		} else {
			// case 1:
			if (start == end) {
				// remove all fractions lies within ff;
				iff = tfr.iterator();
				while (iff.hasNext()) {
					FileFraction frac = iff.next();
					if (frac.getStart() >= start.getStart()
							&& frac.getEnd() <= end.getEnd()) {
						// remove frac
						tfr.remove(frac);
						File tf = frac.getFile();
						tf.delete();
					}
				}
				tfr.add(ff);
				return;
			}
			// case 2
			if (start == ff && end != ff) {
				// remove all fractions lies within ff;
				iff = tfr.iterator();
				while (iff.hasNext()) {
					FileFraction frac = iff.next();
					if (frac.getStart() >= start.getStart()
							&& frac.getEnd() <= start.getEnd()) {
						// remove frac
						tfr.remove(frac);
						File tf = frac.getFile();
						tf.delete();
					}
					if (frac == start || frac == end) {
						iff.remove();
					}
				}
				// merge start and end;
				try{
					mergeFileFraction(start, end);
				}catch(P2PException pe){
					pe.printStackTrace();
				}
				if (!start.isComplete())
					tfr.add(start);
				return;
			}
			// case 3
			if (start != ff && end != ff) {
				iff = tfr.iterator();
				while (iff.hasNext()) {
					FileFraction frac = iff.next();
					if (frac.getStart() >= ff.getStart()
							&& frac.getEnd() <= ff.getEnd()) {
						// remove frac
						tfr.remove(frac);
						File tf = frac.getFile();
						tf.delete();
					}
					if (frac == start || frac == end) {
						iff.remove();
					}
				}
				try{
					mergeFileFraction(start, ff);
					mergeFileFraction(start, end);
				}catch(P2PException pe){
					pe.printStackTrace();
				}
				if (!start.isComplete())
					tfr.add(start);
			}
			// case 4
			if (start != ff && end == ff) {
				iff = tfr.iterator();
				while (iff.hasNext()) {
					FileFraction frac = iff.next();
					if (frac.getStart() >= end.getStart()
							&& frac.getEnd() <= end.getEnd()) {
						// remove frac
						tfr.remove(frac);
						File tf = frac.getFile();
						tf.delete();
					}
					if (frac == start || frac == end) {
						iff.remove();
					}
				}
				try{
					mergeFileFraction(start, end);
				}catch(P2PException pe){
					pe.printStackTrace();
				}
				if (!start.isComplete())
					tfr.add(start);
			}
		}
	}

	private void mergeFileFraction(FileFraction ff1, FileFraction ff2) throws P2PException{
		FileFraction tempf = ff1;
		if (ff1.getStart() > ff2.getStart()) {
			ff1 = ff2;
			ff2 = tempf;
		}
		int stPos = ff1.getEnd();
		long numToSkip = stPos - ff2.getStart();
		mergeFile(ff1.getFile(), ff2.getFile(), numToSkip);
		String fileName = ff1.getFile().getName();
		fileName = fileName.substring(0, fileName.indexOf("]_"));
		fileName = fileName + "]_" + ff1.getStart() + "_" + ff2.getEnd();
		if (ff2.isFileEnd()) {
			fileName += fileNameEnding;
			ff1.setFileEnd(true);
		}
		ff1.getFile().renameTo(new File(path + fileName));
		ff1.setEnd(ff2.getEnd());
		ff1.setFile(new File(path + fileName));
		ff1.setFileName(fileName);
		if (ff1.checkComplete()) {
			// validate the integrity of the file;
			System.out.println("File downloading completed, check integrity...");
			String hashExpected = ff1.getHash();
			String hashActual = MD5.md5code(ff1.getFile());
			if (!hashActual.equalsIgnoreCase(hashExpected)) {
				System.out.println("Expected hash=" + hashExpected
						+ " actual hash=" + hashActual.toUpperCase());
				System.out.println("file corrupted");
				ff1.getFile().delete();
				throw new P2PException("file corrupted");
			}
			System.out.println("File passed integrity check.");
			String tempName = ff1.getFileName();
//			tempName = tempName.substring(0, tempName.indexOf(".txt[") + 4);
			int index=0;
			while(index!=tempName.length()){
				if(tempName.charAt(index)=='['&&tempName.charAt(index+33)==']')break;
				++index;
			}
			tempName=tempName.substring(0,index);
//			System.out.println(tempName);
			ff1.getFile().renameTo(new File(path + tempName));
			ff1.setFileName(tempName);
			ff1.setFile(new File(path + tempName));
			System.out.println("File "+tempName+" is completely downloaded.");
		}else{ 
			System.out.println("File not completed");
		}
	}

	private void mergeFile(File f1, File f2, long skip) { // append file 2 to
															// file 1 and delete
															// the original file
															// 2, skip--number of bytes we skip at the front of file2
		if (!f1.exists() || !f2.exists())
			return;
		try {
			FileInputStream fis = new FileInputStream(f2);
			FileOutputStream fos = new FileOutputStream(f1, true);
			byte[] buffer = new byte[1024];
//			System.out.println("merge file " + f1.getName() + " "
//					+ f2.getName());
//			System.out.println("skip file by " + skip + " bits");
			fis.skip(skip);
			int len = 0;
			while ((len = fis.read(buffer)) != -1) {
				fos.write(buffer, 0, len);
			}
			fis.close();
			fos.close();
			f2.delete();
		} catch (IOException ie) {
			ie.printStackTrace();
		}
	}

	public void storeFileContent(FileContentCmd fcc) {
		if(fcc==null)return;
		File file = null;
		if (!fcc.isCompleteFlag()) {
			file = new File(path + fcc.getFilename() + "[" + fcc.getMd5Hash()
					+ "]_" + fcc.getStartOffset() + "_" + fcc.getEndOffset());
		} else {
			file = new File(path + fcc.getFilename() + "[" + fcc.getMd5Hash()
					+ "]_" + fcc.getStartOffset() + "_" + fcc.getEndOffset()
					+ fileNameEnding);
		}
		try {
			file.createNewFile();
//			PrintWriter out = new PrintWriter(file);
//			out.print(fcc.getFileContent()); //print or println
//			out.close();
			FileOutputStream fos=new FileOutputStream(file);
			sun.misc.BASE64Decoder de = new sun.misc.BASE64Decoder();
			fos.write(de.decodeBuffer(fcc.getFileContent()));
			fos.close();
			try {
				FileFraction ff = new FileFraction(file);
				mergeFileFraction(ff);
			} catch (Exception e) {
				e.printStackTrace();
			}
		} catch (IOException ie) {
			ie.printStackTrace();
		}
	}

	public static void main(String[] args) {
		File f2 = new File("testfile.txt[hash]_4_10");
		FileFraction ff2 = null;
		try {
			ff2 = new FileFraction(f2);
			FileManager fm = new FileManager();
			fm.mergeFileFraction(ff2);
		} catch (Exception e) {
			e.printStackTrace();
			return;
		}
	}
}
